#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <pthread.h>
#include <math.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include "bashutils.h"

#define UNUSED(x)
#define PROGRESS_PREFIX_SUFFIX_CHARS (5)

/*
 * initializes the progress struct
 * @param p      a pointer to an progress struct
 * @param min    the minimum value of the progress range
 * @param max    the maximum value of the progress range
 * @return progress error code
 */
int progress_init(progress_t* p, long long min, long long max)
{
    if (NULL == p)
        return PERR_INV_PRG;

    p->placeholder = NULL;

    if (min >= max)
        return PERR_INV_RANGE;

    p->minimum = min;
    p->maximum = max;

    max = abs(max);
    int digitnum = 0;
    while (max)
    {
        max /= 10;
        ++digitnum;
    }

    struct winsize w;
    ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
    // digits and %
    p->width = w.ws_col - digitnum - PROGRESS_PREFIX_SUFFIX_CHARS;
    p->width = p->width > 0 ? p->width : 0;
    p->placeholder = (char*)malloc(p->width + 1);

    if (p->placeholder == NULL)
        return PERR_INV_PLACE;

    memset(p->placeholder, ' ', p->width);
    memset(p->placeholder + p->width, 0, 1);

    return PERR_OK;
}

/*
 * show progress when the range value changes
 * @param p      a pointer to an progress struct
 * @param value  the changed value
 * @return progress error code
 */
int progress_show(progress_t* p, long long value)
{
    if (!isatty(fileno(stdout)))
        return PERR_INV_OSTM;

    if (NULL == p)
        return PERR_INV_PRG;

    if (p->width <= 0)
        return PERR_INV_WIDTH;

    if (p->placeholder == NULL)
        return PERR_INV_PLACE;

    long long range = abs(p->maximum - p->minimum);
    if (range <= 0)
        return PERR_INV_RANGE;

    if (value < p->minimum || value > p->maximum)
        return PERR_INV_VALUE;

    double factor = (double)(value - p->minimum) / range;

    int pos = factor * p->width;
    memset(p->placeholder, '#', pos);

    int percent = factor * 100;
    printf("\e[?25l[%-s] %d%%\e[0m\r", p->placeholder, percent);
    fflush(stdout);
}

/*
 * frees the memory space which have been allocated by the progress
 * @param p     a pointer to an progress struct
 */
void progress_destory(progress_t* p)
{
    if (NULL != p)
    {
        if (isatty(fileno(stdout)))
        {
            printf("\e[?25h\e[K");
            fflush(stdout);
        }

        free(p->placeholder);
        p->placeholder = NULL;
    }
}

/*---------------------------------------------------
 * status bar
 * note: the struct will be registered SIGALRM signal,
 * and be registered SIG_DFL after stop.
 *---------------------------------------------------*/
typedef struct {
    int   rate;         // the refresh rate in second
    int   width;        // the paceholder number of the status display
    int   inited;       // whether the status initialized
    int   started;      // whether the status started
    char* placeholder;  // the string of the placeholder
}status_t;

status_t g_statusbar = {0, 0, 0, 0, NULL};

/*
 * initializes the status
 * @param rate   the refresh rate in second
 * @param width  the paceholder number of the progress display
 * @return status error code
 */
int status_init(int rate)
{
    if (g_statusbar.inited)
        return SERR_RE_INIT;

    bzero(&g_statusbar, sizeof(g_statusbar));

    if (rate <= 0)
        return SERR_INV_RATE;

    struct winsize w;
    ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);

    int width = w.ws_col;

    if (width <= 0)
        return SERR_INV_WIDTH;

    g_statusbar.placeholder = (char*)malloc(width + 1);
    if (NULL == g_statusbar.placeholder)
        return SERR_INV_WIDTH;

    bzero(g_statusbar.placeholder, width + 1);

    g_statusbar.rate = rate;
    g_statusbar.width = width;
    g_statusbar.inited = 1;

    return SERR_OK;
}

void refresh_status(int signo)
{
    UNUSED(signo);

    if (g_statusbar.width > 0 && g_statusbar.placeholder != NULL)
    {
        int len = strlen(g_statusbar.placeholder);
        len = (len + 1) % g_statusbar.width;

        memset(g_statusbar.placeholder, 0, g_statusbar.width);
        memset(g_statusbar.placeholder, '=', len);

        if (isatty(fileno(stdout)))
        {
            printf("\e[K\e[?25l%-s\e[0m\r", g_statusbar.placeholder);
            fflush(stdout);
        }
    }
}

/*
 * start the status
 */
int status_start()
{
    if (!g_statusbar.inited)
        return SERR_NOT_INIT;

    if (g_statusbar.started)
        return SERR_STARTED;

    g_statusbar.started = 1;

    signal(SIGALRM, refresh_status);

    struct itimerval itv;
    itv.it_interval.tv_sec = g_statusbar.rate;
    itv.it_interval.tv_usec = 0;
    itv.it_value.tv_sec = 1;
    itv.it_value.tv_usec = 0;

    setitimer(ITIMER_REAL, &itv, NULL);

    return SERR_OK;
}

/*
 * stop the status
 */
int status_stop()
{
    if (!g_statusbar.inited)
        return SERR_NOT_INIT;

    if (!g_statusbar.started)
        return SERR_NOT_START;

    if (isatty(fileno(stdout)))
    {
        printf("\e[?25h\e[K");
        fflush(stdout);
    }

    g_statusbar.started = 0;

    signal(SIGALRM, SIG_DFL);

    struct itimerval itv;
    itv.it_interval.tv_sec = 0;
    itv.it_interval.tv_usec = 0;
    itv.it_value.tv_sec = 0;
    itv.it_value.tv_usec = 0;

    setitimer(ITIMER_REAL, &itv, NULL);

    return SERR_OK;
}

/*
 * destory the status
 */
int status_destory()
{
    if (!g_statusbar.inited)
        return SERR_OK;

    if (g_statusbar.started)
        return SERR_STARTED;

    if (g_statusbar.placeholder != NULL)
        free(g_statusbar.placeholder);

    bzero(&g_statusbar, sizeof(g_statusbar));

    return SERR_OK;
}

/*
 * repeatedly overlap the text on the last single line
 */
void echo_overlap(const char* s)
{
    if (s == NULL)
        return;

    if (!isatty(fileno(stdout)))
    {
        printf("%s\n", s);
    }
    else
    {
        printf("\e[K%s\r", s);
        fflush(stdout);
    }
}

/*
 * display a line text in common style
 */
void echo_info(const char* s)
{
    if (s == NULL)
        return;

    printf("%s", s);

    if (isatty(fileno(stdout)))
    {
        fflush(stdout);
    }
}

/*
 * display a line text in green color
 */
void echo_sucess(const char* s)
{
    if (s == NULL)
        return;

    if (!isatty(fileno(stdout)))
    {
        printf("%s", s);
    }
    else
    {
        printf("\e[32m%s\e[0m", s);
        fflush(stdout);
    }
}

/*
 * display a line text in yellow color
 */
void echo_warn(const char* s)
{
    if (s == NULL)
        return;

    if (!isatty(fileno(stdout)))
    {
        printf("%s", s);
    }
    else
    {
        printf("\e[33m%s\e[0m", s);
        fflush(stdout);
    }
}

/*
 * display a line text in red color
 */
void echo_error(const char* s)
{
    if (s == NULL)
        return;

    if (!isatty(fileno(stdout)))
    {
        printf("%s", s);
    }
    else
    {
        printf("\e[31m%s\e[0m", s);
        fflush(stdout);
    }
}

/*
 * display a line text with underline
 */
void echo_underline(const char* s)
{
    if (s == NULL)
        return;

    if (!isatty(fileno(stdout)))
    {
        printf("%s", s);
    }
    else
    {
        printf("\e[4m%s\e[0m", s);
        fflush(stdout);
    }
}

/*
 * display a line text with reversed color
 */
void echo_reverse(const char* s)
{
    if (s == NULL)
        return;

    if (!isatty(fileno(stdout)))
    {
        printf("%s", s);
    }
    else
    {
        printf("\e[7m%s\e[0m", s);
        fflush(stdout);
    }
}
